;; The first four lines of this file were added by Dracula.
;; They tell DrScheme that this is a Dracula Modular ACL2 program.
;; Leave these lines unchanged so that DrScheme can properly load this file.
#reader(planet "reader.rkt" ("cce" "dracula.plt") "modular" "lang")
;@author Youming Lin, Matt Rainwater, Calvin Nelms
;@date Apr 8, 2012
;@version 1.0

(require "specifications.lisp")

;MBlur module blurs the entire image
(module MBlur
  (import IImage)
  (import IColor)
  (import IMath)
  
  ;gaussian function given a distance and a radius
  ;@param dist - distance from the start point
  ;@param radius - radius equal to 1/3 user-given distance
  ;@return float - gaussian weight
  (defun gaussian (dist-x dist-y radius)
    (let* ((denom (* (* 2 3.1415926535897932384626433832795028842) (* radius radius)))
           (exp   (/ (+ (* dist-x dist-x) (* dist-y dist-y)) (* 2 (* radius radius))))
           (e     (Expt (n-root (denominator exp) 2.71828183) (* -1 (numerator exp)))))
         (/ e denom)
    )
  )
  
  ;multiplies each color's rgb values with its gaussian weight
  ;@param colors - list of colors with their x/y distances
  ;@param k - radius equal to 1/3 user-given distance
  ;@return list - list of weighted colors
  (defun weight-colors (colors k)
    (if (consp colors)
        (let* ((color (caar colors))
               (r (get-r color))
               (g (get-g color))
               (b (get-b color))
               (g-value (gaussian (cadar colors) (caddar colors) (/ k 3))))
               (cons (set-rgb (list (* r g-value)
                                    (* g g-value)
                                    (* b g-value)))
                     (weight-colors (cdr colors) k))
        )
        nil
    )
  )
               
  
  ;function that pairs colors with their distances
  ;@param x - x point to start at
  ;@param y - y point to start at
  ;@param points - list of points near (x,y)
  ;@return list - list of (color . distance)
  (defun get-color-distance-pairs (img x y points)
    (if (consp points)
        (cons (list (get-color (caar points) (cdar points) img)
                    (abs (- (caar points) x)) 
                    (abs (- (cdar points) y)))
                    (get-color-distance-pairs img x y (cdr points)))
        nil
    )
  )
  
  ;helper function that gets the k-nearest neighboring pixels
  ;to (start-x, start-y)
  ;@param k - radius of pixels
  ;@param start-x - x coordinate of starting point
  ;@param start-y - y coordinate of starting point
  ;@param x - current x coordinate looked at
  ;@param y - current y coordinate looked at
  ;@return list - list of points
  (defun get-k-nearest-points-helper (k start-x start-y x y cur-list)
    (if (<= x k)
        (if (<= y (- k x))
            (get-k-nearest-points-helper k start-x start-y x (+ 1 y) 
              (append cur-list (list (cons (- start-x x) start-y)
                                     (cons (+ start-x x) start-y)
                                     (cons start-x (- start-y y))
                                     (cons start-x (+ start-y y))
                                     (cons (- start-x x) (- start-y y))
                                     (cons (+ start-x x) (- start-y y))
                                     (cons (- start-x x) (+ start-y y))
                                     (cons (+ start-x x) (+ start-y y)))))
            (get-k-nearest-points-helper k start-x start-y (+ x 1) 0 cur-list)
        )
        cur-list
      )
    )
  
  ;function that removes negative (and thus
  ;unecessary) points
  ;@param points - list of points to iterate through
  ;@param points-removed - list that contains points that have been shown to not be negative
  ;and points that haven't been looked at yet
  ;@return list - list of non-negative points
  (defun remove-negative-points (points points-removed height width)
    (if (consp points)
        (if (or (or (< (caar points) 0) (< (cdar points) 0)) 
                (or (>= (caar points) width) (>= (cdar points) height)))
            (remove-negative-points (cdr points) (remove-equal (car points) points-removed) height width)
            (remove-negative-points (cdr points) points-removed height width)
        )
        points-removed
    )
  )
  
  ;helper function that gets the k-nearest neighboring pixels
  ;to (start-x, start-y)
  ;@param k - radius of pixels
  ;@param start-x - x coordinate of starting point
  ;@param start-y - y coordinate of starting point
  ;@return list - list of points
  (defun get-k-nearest-points (k start-x start-y img)
    (remove-negative-points (remove-duplicates-equal (get-k-nearest-points-helper k start-x start-y 0 0 nil))
                            (remove-duplicates-equal (get-k-nearest-points-helper k start-x start-y 0 0 nil)) 
                            (img-height img) (img-width img))
  )
  
  (defun get-rs (colors)
    (if (consp colors)
        (cons (get-r (car colors)) (get-rs (cdr colors)))
        nil
    )
  )
  
  (defun get-gs (colors)
    (if (consp colors)
        (cons (get-g (car colors)) (get-gs (cdr colors)))
        nil
    )
  )
  
  (defun get-bs (colors)
    (if (consp colors)
        (cons (get-b (car colors)) (get-bs (cdr colors)))
        nil
    )
  )
  
  ;helper function that iterates through the entire image
  ;and blurs every pixel
  ;@param img1 - orginal image
  ;@param img2 - new image
  ;@param x - current x location
  ;@param y - current y location
  ;@return image - newly blurred image
  (defun blend-color (img1 img2 x y k)
    (if (is-image-empty? img1)
        img1
        (if (and (natp x) (natp y) (< y (img-height img1)))
            (if (< x (img-width img1))
                (let* ((points (get-k-nearest-points k x y img1))
                       (colors (get-color-distance-pairs img1 x y points))
                       (weighted-colors (weight-colors colors k))
                       (avg-r (sum (get-rs weighted-colors)))
                       (avg-g (sum (get-gs weighted-colors)))
                       (avg-b (sum (get-bs weighted-colors))))
                  (blend-color img1
                               (add-pixel x
                                          y
                                          (set-rgb (list avg-r
                                                         avg-g
                                                         avg-b))
                                          img2)
                               (1+ x)
                               y k))
                (blend-color img1 img2 0 (1+ y) k))
            img2)))
  
  ;blur blurs the entire image
  ;@param img - original image
  ;@return image - newly blurred image
  (defun blur (img k)
    (if (is-image-empty? img)
        img
        (blend-color img
                     (empty-image (img-width img) (img-height img))
                     0
                     0 
                     k)))
  
  (export IBlur))